-
-
Notifications
You must be signed in to change notification settings - Fork 504
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add generic reference as replacement for DBRef #1623
Conversation
references are not compatible with the discriminators. | ||
storeAs - Indicates how to store the reference. ``id`` stores the identifier, | ||
``ref`` an embedded object containing the ``id`` field and (optionally) a | ||
discriminator. ``dbRef`` and ``dbRefWithDb`` store a ``DBRef`` object. They |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we keep the DBRef documentation link?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we should. Will update that.
``dbRef`` uses a `DBRef`_ without ``$db`` value and ``dbRefWithDb`` stores | ||
a full `DBRef`_ (``$ref``, ``$id``, and ``$db``). Note that ``id`` | ||
``ref`` stores fields ``id`` and ``ref`` (similar to DBRef without $ prefix) value and ``refWithDb`` stores | ||
a additionally db as parameter. ``dbRef`` and ``dbRefWithDb`` use ``DBRef``, they are deprecated in favor of ref and refWithDb. Note that ``id`` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason for this to not simply repeat the @ReferenceMany
documentation for storeAs
?
@@ -360,6 +360,7 @@ The ``storeAs`` option has three possible values: | |||
|
|||
- **dbRefWithDb**: Uses a `DBRef`_ with ``$ref``, ``$id``, and ``$db`` fields (this is the default) | |||
- **dbRef**: Uses a `DBRef`_ with ``$ref`` and ``$id`` | |||
- **ref**: Uses a custom embedded object with an ``id`` field | |||
- **id**: Uses a ``MongoId`` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The id
documentation should likely be changed to "Uses the identifier" instead of "MongoId" to be consistent with the change you made for @ReferenceMany
and storeAs
.
break; | ||
|
||
default: | ||
throw MappingException::cannotLookupNonIdReference($this->class->name, $fieldName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is repeated for $referenceMapping['storeAs']
above and $mappedByMapping['storeAs']
here, would it make sense to extract this to a helper method?
$dbRef = array( | ||
'$ref' => $class->getCollection(), | ||
'$id' => $class->getDatabaseIdentifierValue($id), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit rusty, but is DocumentManager::createDBRef()
to generic method for creating a reference to another document? I assume it's no longer specific to DBRef objects since the introduction of simple references some time ago.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name would suggest it creates a DBRef
object, but it was used to create whatever reference was required according to the mapping. I'd suggest deprecating this and introducing a new createReference
method to take its place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Introduced createReference
which also makes the $referenceMapping
argument mandatory. I've also replaced all occurences of createDBRef
with createReference
, hoping I didn't create a BC break with that because people expect createDBRef
to be called.
@@ -722,7 +722,7 @@ private function loadReferenceManyCollectionOwningSide(PersistentCollectionInter | |||
$mongoId = $reference; | |||
} else { | |||
$className = $this->uow->getClassNameForAssociation($mapping, $reference); | |||
$mongoId = $reference['$id']; | |||
$mongoId = ClassMetadataInfo::getReferenceId($reference, $mapping['storeAs']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Earlier, I noted that you may want to add the array
type hint to getReferenceId()
's first parameter. Alternatively, it may be worth changing getReferenceId()
and UnitOfWork::getClassNameForAssociation()
to work on simple references, which would let you remove this if/else entirely and simply assign $className
and $mongoId
the same for all reference types.
if (isset($mappedByMapping['storeAs']) && $mappedByMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID) { | ||
$mappedByFieldName = $mapping['mappedBy']; | ||
} else { | ||
$mappedByFieldName = $mapping['mappedBy'] . '.' . ClassMetadataInfo::getReferencePrefix(isset($mappedByMapping['storeAs']) ? $mappedByMapping['storeAs'] : null) . 'id'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, perhaps we can have a single method here that returns the mappedByFieldName
for any reference type. That may allow us to keep getReferencePrefix()
private.
@@ -1424,20 +1429,25 @@ private function getWriteOptions(array $options = array()) | |||
private function prepareDbRefElement($fieldName, $value, array $mapping, $inNewObj) | |||
{ | |||
$dbRef = $this->dm->createDBRef($value, $mapping); | |||
if ($inNewObj) { | |||
if ($inNewObj || $mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite recall what prepareDbRefElement()
is used for, but these changes appear to be correct. I assume discriminator values (if applicable) are added later, since this method only deals with basic keys for database references?
@@ -255,6 +255,7 @@ private function addManyReferences(PersistentCollectionInterface $persistentColl | |||
{ | |||
$mapping = $persistentCollection->getMapping(); | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary line break?
public function testLookupStageReferenceManyStoreAsRef() | ||
{ | ||
$this->requireMongoDB32('$lookup tests require at least MongoDB 3.2.0'); | ||
$this->insertTestData(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense for this to go in the setUp()
function for the test class, or is it not needed by each test case?
51ad6f0
to
c2cc628
Compare
@jmikola took some time to work on this and applied most of your feedback. I still need to check for discriminator handling in |
Note: build fails due to new driver version. I'll take a look at that in master branch. |
storeAs - Indicates how to store the reference. ``id`` stores the identifier, | ||
``ref`` an embedded object containing the ``id`` field and (optionally) a | ||
discriminator. ``dbRef`` and ``dbRefWithDb`` store a `DBRef`_ object. They | ||
are deprecated in favor of ``ref``. Note that ``id`` references are not |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For clarity, I'd replace ". They" with "and" to combine these sentences.
- | ||
cascade - Cascade Option | ||
- | ||
discriminatorField - The field name to store the discriminator value within | ||
the `DBRef`_ object. | ||
the reference object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this specify the logical conflict with id
mapping, or is that discussed elsewhere? I assume we throw an exception for such mappings at runtime in any event.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's specified where we explain the different storeAs
options:
Note that
id
references are not compatible with the discriminators.
I can add another note to each discriminator option for clarity.
switch ($storeAs) { | ||
case ClassMetadataInfo::REFERENCE_STORE_AS_ID: | ||
if ($class->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION) { | ||
throw MappingException::simpleReferenceMustNotTargetDiscriminatedDocument($referenceMapping['targetDocument']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This appears to be the exception I was expecting for the id
strategy and discriminated reference conflict.
$dbRef = ['id' => $class->getDatabaseIdentifierValue($id)]; | ||
break; | ||
|
||
default: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be safer to explicitly handle both cases here and leave default:
in place to throw an exception for an unsupported strategy. That keeps the code defensive in case we ever add more strategies down the line.
|
||
|
||
case ClassMetadataInfo::REFERENCE_STORE_AS_REF: | ||
$dbRef = ['id' => $class->getDatabaseIdentifierValue($id)]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to rename this local variable to $ref
, since this was the original name used for two DBRef strategies handled by this function?
@@ -73,28 +73,34 @@ public function references($document) | |||
{ | |||
if ($this->currentField) { | |||
$mapping = $this->getReferenceMapping(); | |||
$dbRef = $this->dm->createDBRef($document, $mapping); | |||
$dbRef = $this->dm->createReference($document, $mapping); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename to $ref
?
$keys = array('ref' => true, 'id' => true, 'db' => true); | ||
if ($storeAs === ClassMetadataInfo::REFERENCE_STORE_AS_REF) { | ||
$keys = ['id' => true]; | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another place we might want to be defensive about $storeAs
values.
} | ||
} | ||
} else { | ||
$dbRef = $this->dm->createDBRef($document); | ||
@trigger_error('Calling ' . __METHOD__ . ' without a current field set will no longer be possible in ODM 2.0.', E_USER_DEPRECATED); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question as before about role of @
.
$keys = array('ref' => true, 'id' => true, 'db' => true); | ||
if ($storeAs === ClassMetadataInfo::REFERENCE_STORE_AS_REF) { | ||
$keys = ['id' => true]; | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defensive opportunity.
/** | ||
* @before | ||
*/ | ||
public function prepareTest() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this an alternative to setUp()
and calling parent::setUp()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly - not sure where the advantages of each are, but I've started using @before
and @after
instead of setUp
and tearDown
in test classes and only use the latter in abstract TestCase classes.
fbfc8ce
to
761c496
Compare
761c496
to
40cb88e
Compare
This avoids repeatedly checking for the type of reference and duplicating logic. It also allows us to make getReferencePrefix private.
40cb88e
to
0ac42f6
Compare
{ | ||
@trigger_error('The ' . __METHOD__ . ' method has been deprecated and will be removed in ODM 2.0. Use createReference() instead.', E_USER_DEPRECATED); | ||
|
||
if (!isset($referenceMapping['storeAs'])) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be fixed on class metadata level?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's a public method I decided to be defensive regarding what we receive. There's no guarantee someone will pass an actual mapping, they could call it with a partial mapping array (e.g. our tests). One more reason to look forward to metadata objects.
$this->query[$mapping['name']] = $dbRef; | ||
} else { | ||
$keys = array('ref' => true, 'id' => true, 'db' => true); | ||
switch ($storeAs) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seem to be very similar switch
to one from DocumentPersister
- maybe this should be merged somehow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can try and extract the logic to a common method - maybe I can use DocumentManager::createReference
for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No big deal if it's complicated or anything, just occurred to me while reading :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah, it's worth a shot :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out the methods are similar to suggest extracting the logic to a separate method, but different enough to not really change anything. Decided to skip it after all.
$this->query[$mapping['name']] = $dbRef; | ||
} else { | ||
$keys = array('ref' => true, 'id' => true, 'db' => true); | ||
switch ($storeAs) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
Supersedes #1566.
As discussed in our last hangout, this is what remains of the DBRef replacement. DBRef is not deprecated (yet), athough we might decide to deprecate it at this time. At this time the PR is feature complete, I'd like to write a whole bunch of tests to ensure the whole discriminator handling/omitting target collection name works as I'd expect it to.
The new reference object only stores an ID and optionally a discriminator field. The main difference to DBRef is that it can be used in aggregation pipeline stages that don't handle DBRef objects because of the
$
in field names (e.g.$lookup
and$graphLookup
).I'll also revisit #1527 to see if we can store additional fields in the reference (which would also apply to DBRef objects).